﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using CoreEx.Common.Data;
using CoreEx.Database.Metadata;
using CoreEx.Database.Query;
using CoreEx.Database.Query.Linq;
using LinFu.IoC;
using LinFu.IoC.Interfaces;
using LinFu.Reflection;
using CoreEx.MicroMapper;
using CoreEx.Common.Extensions;
using System.Reflection;
namespace CoreEx.Common.Repository.Implementation
{
    public class Repository_old : IRepository, IInitialize<IServiceRequestResult>
    {
        private ITypeMappingRepository _mappingRepository;
        private IServiceContainer _serviceContainer;
        private IExpressionFactory _expressionFactory;
        private IExpressionTranslator _expressionTranslator;
        private IAliasMap _aliasMap;
        private string _repositoryName;
        private IDictionary<PropertyInfo, Expression> _childRelations = new Dictionary<PropertyInfo, Expression>(); 
       
        private IDictionary<PropertyInfo,Expression> _propertyExpressions = new Dictionary<PropertyInfo, Expression>();


        private IMetadataRepository _metadataRepository;

        private IDictionary<MemberInfo, Expression> _expressionMap = new Dictionary<MemberInfo, Expression>();
        
        private IDictionary<Type,Type> _relationMap = new Dictionary<Type, Type>();
        private HashSet<Expression> _appliedExpressions = new HashSet<Expression>();


        public void LoadWith<T>(Expression<Func<T, object>> expression)
        {            
            EvaluateLoadExpression(expression);
            
        }

        public IList<T> Find<T>(Expression<Func<T, bool>> expression, params Expression<Func<T, object>>[] loadExpressions)
        {




            _expressionMap.Add(typeof(T), expression);
            //Evaluate all loadexpressions
            loadExpressions.ForEach(EvaluateLoadExpression);
            LoadTable(typeof(T), null);
            

            


                        return null;
        }

        
        private ITable LoadTable(Type type, Type parentType)
        {                        
            //Create the projection
            var selectQueryExpression = CreateProjection(type);
            _relationMap.Add(type,parentType);
            _appliedExpressions.Clear();
            CreateJoinExpression(selectQueryExpression,type);
            //Get the expression/filter to apply to this type
            if (_expressionMap.ContainsKey(type) && parentType == null)
            {
                var expression = _expressionMap[type];
                var whereExpression = _expressionTranslator.Translate(expression);
                selectQueryExpression.Where(whereExpression);
            }

            var table = _serviceContainer.GetService<IDbCommand>(_repositoryName).ExecuteReader(selectQueryExpression).ToTable();            
            
            //Find childreleations that has this type as its declaring type 
            var relations = _childRelations.Where(c => c.Key.DeclaringType == type);
            foreach (var relation in relations)
            {
                _aliasMap.Clear();
                LoadTable(TypeSystem.GetElementType(relation.Key.PropertyType),type);                
            }

            return table;
        }


        

       

        private void CreateJoinExpression(SelectQueryExpression selectQueryExpression,Type type)
        {
            var parentType = _relationMap[type];

            if (parentType == null)
                return;
            

            var tableInfo = _mappingRepository[type].TableInfo;

            var tableAlias = _aliasMap.GetCurrentAlias(tableInfo.TableName);

            //Find the mapping information that relates to the declaring type.
            var parentTableInfo = _mappingRepository[parentType].TableInfo;

            var parentTableAlias = _aliasMap.GetNextAlias(parentTableInfo.TableName);

            //Find the foreign key that refers to the declaring type
            var foreignkey = tableInfo.ForeignKeys.Where(fk => fk.PrimaryTable == parentTableInfo).FirstOrDefault();

            IExpression expression = null;
            foreach (var foreignKeyColumn in foreignkey.Columns)
            {
                var conditionExpression = _expressionFactory.Column
                        (tableAlias,foreignKeyColumn.ForeignColumnInfo.ColumnName) == 
                        _expressionFactory.Column(parentTableAlias,foreignKeyColumn.PrimaryColumnInfo.ColumnName);
                if (expression == null)
                    expression = conditionExpression;
                else
                    expression = (ExpressionBase)expression & conditionExpression;                                
            }

            if (_expressionMap.ContainsKey(parentType) && !_appliedExpressions.Contains(_expressionMap[parentType]))
            {
                //Apply the parent expression filter
                expression = (ExpressionBase)expression &
                             (ExpressionBase)_expressionTranslator.Translate(_expressionMap[parentType]);
                _appliedExpressions.Add(_expressionMap[parentType]);
            }

            if (_expressionMap.ContainsKey(type) && !_appliedExpressions.Contains(_expressionMap[type]))
            {
                //Apply the expression filter related to this type.
                expression = (ExpressionBase)expression &
                             (ExpressionBase)_expressionTranslator.Translate(_expressionMap[type]);
                _appliedExpressions.Add(_expressionMap[type]);
            }

            selectQueryExpression.InnerJoin(
                _expressionFactory.Alias(_expressionFactory.Table(parentTableInfo.TableName), parentTableAlias), expression);
                
            CreateJoinExpression(selectQueryExpression,_relationMap[type]);
        }


        



        private void EvaluateLoadExpression<T>(Expression<Func<T, object>> expression)
        {
            if (expression.Body.NodeType == ExpressionType.MemberAccess)
                _childRelations.Add((PropertyInfo)((MemberExpression)expression.Body).Member,expression);
            
            if (expression.Body.NodeType == ExpressionType.Call)
            {
                var methodCallExpression = (MethodCallExpression) expression.Body;
                var type = TypeSystem.GetElementType(
                    ((PropertyInfo) ((MemberExpression) methodCallExpression.Arguments[0]).Member).PropertyType);

                _expressionMap.Add(type, methodCallExpression.Arguments[1]);                
                _childRelations.Add((PropertyInfo)((MemberExpression)methodCallExpression.Arguments[0]).Member,expression);
            }
        }




        public IList<T> Find<T>(Expression<Func<T, bool>> expression)
        {
            _expressionMap.Add(typeof(T),expression);
            
            var table = LoadTable(typeof(T),null);
            
            
            
            //var selectQuery = CreateProjection(typeof(T));
            //var whereExpression = _expressionTranslator.Translate(expression);
            //selectQuery.Where(whereExpression);
            //var table = _serviceContainer.GetService<IDbCommand>(_repositoryName).ExecuteReader(selectQuery).ToTable();
            //var mapper = _serviceContainer.GetService<ITableMapper<T>>();            
            //mapper.TargetFactory = (row) => _serviceContainer.GetService<T>();
            //return mapper.Apply(table).ToList();                               
            return null;
        }

        public IList<T> FindAll<T>()
        {
            var expression = CreateProjection(typeof (T));
            var table = _serviceContainer.GetService<IDbCommand>(_repositoryName).ExecuteReader(expression).ToTable();
            var mapper = _serviceContainer.GetService<ITableMapper<T>>();
            mapper.TargetFactory = (row) => _serviceContainer.GetService<T>();
            return mapper.Apply(table).ToList();                       
        }

        public IList<T> FindAll<T>(params Expression<Func<T, object>>[] loadWith)
        {
            
            throw new System.NotImplementedException();
        }

        public void Save(IList<IObjectState> changeRequests)
        {
            throw new System.NotImplementedException();
        }

        private SelectQueryExpression CreateProjection(Type type)
        {
            var query = _serviceContainer.GetService<IQuery>();                         
            var tableMappingInfo = _mappingRepository[type];
            var alias = _aliasMap.GetNextAlias(tableMappingInfo.TableInfo.TableName);
            var projection = tableMappingInfo.ColumnMappings.Values.Where(cm => cm.ColumnInfo != null).Select(
                cm => _expressionFactory.Column(alias, cm.ColumnInfo.ColumnName)).ToList();
            return query.Select(projection.ToArray()).From(e => e.Alias(e.Table(tableMappingInfo.TableInfo.TableName),alias));                             
        }


        public void Initialize(IServiceRequestResult source)
        {
            _expressionFactory = source.Container.GetService<IExpressionFactory>();
            _metadataRepository = source.Container.GetService<IMetadataRepository>(source.ServiceName);
            _mappingRepository = source.Container.GetService<ITypeMappingRepository>(source.ServiceName);
            _expressionTranslator = source.Container.GetService<IExpressionTranslator>(source.ServiceName);
            _aliasMap = source.Container.GetService<IAliasMap>();
            _repositoryName = source.ServiceName;
            _serviceContainer = source.Container;
        }
    }
}
